package com.zhangmeng.tools.utils;


import com.cronutils.model.Cron;
import com.cronutils.model.CronType;
import com.cronutils.model.definition.CronDefinition;
import com.cronutils.model.definition.CronDefinitionBuilder;
import com.cronutils.model.time.ExecutionTime;
import com.cronutils.parser.CronParser;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;


/**
 * @author : 芊芊墨客
 * @version : 1.0
 * @date : 2023-03-06 15:13
 */
public class CronUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(CronUtil.class);

    private static final String QUESTION = "?";

    private static final String ASTERISK = "*";

    private static final String COMMA = ",";

    /**
     * 替换 分钟、小时、日期、星期
     */
    private static final String ORIGINAL_CRON = "0 %s %s %s * %s";

    /**
     * 检查cron表达式的合法性
     *
     * @param cron cron exp
     * @return true if valid
     */
    public static boolean checkValid(String cron) {
        try {
            // SPRING应该是使用最广泛的类型,但假若任务调度依赖于xxl-job平台,则需要调整为CronType.QUARTZ
            CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(CronType.SPRING);
            CronParser parser = new CronParser(cronDefinition);
            parser.parse(cron);
        } catch (IllegalArgumentException e) {
            LOGGER.error(String.format("cron=%s not valid", cron));
            return false;
        }
        return true;
    }

    public static String buildCron(List<Integer> minutes, List<Integer> hours, List<Integer> weekdays) {
        String minute;
        if (minutes.equals(CronUtil.getInitMinutes())) {
            minute = ASTERISK;
        } else {
            minute = StringUtils.join(minutes, COMMA);
        }
        String hour;
        if (hours.equals(CronUtil.getInitHours())) {
            hour = ASTERISK;
        } else {
            hour = StringUtils.join(hours, COMMA);
        }
        String weekday;
        if (weekdays.equals(CronUtil.getInitWeekdays())) {
            weekday = QUESTION;
        } else {
            weekday = StringUtils.join(weekdays, COMMA);
        }
        // 重点：星期和日字段冲突，判断周日的前端输入
        if (weekday.equals(QUESTION)) {
            return String.format(ORIGINAL_CRON, minute, hour, ASTERISK, weekday);
        } else {
            return String.format(ORIGINAL_CRON, minute, hour, QUESTION, weekday);
        }
    }

    /**
     * 解析db cron expression展示到前端
     *
     * @param cron cron
     * @return minutes/hours/weekdays
     */
    public static CustomCronField parseCon(String cron) {
        if (!CronUtil.checkValid(cron)) {
            return null;
        }
        List<String> result = Arrays.asList(cron.trim().split(" "));
        CustomCronField field = new CustomCronField();
        if (result.get(1).contains(COMMA)) {
            field.setMinutes(Arrays.stream(result.get(1).split(COMMA)).map(Integer::parseInt).collect(Collectors.toList()));
        } else if (result.get(1).equals(ASTERISK)) {
            field.setMinutes(CronUtil.getInitMinutes());
        } else {
            field.setMinutes(new ArrayList(Integer.parseInt(result.get(1))));
        }
        if (result.get(2).contains(COMMA)) {
            field.setHours(Arrays.stream(result.get(2).split(COMMA)).map(Integer::parseInt).collect(Collectors.toList()));
        } else if (result.get(2).equals(ASTERISK)) {
            field.setHours(CronUtil.getInitHours());
        } else {
            field.setHours(new ArrayList(Integer.parseInt(result.get(2))));
        }
        if (result.get(5).contains(COMMA)) {
            field.setWeekdays(Arrays.stream(result.get(5).split(COMMA)).map(Integer::parseInt).collect(Collectors.toList()));
        } else if (result.get(5).equals(QUESTION)) {
            field.setWeekdays(CronUtil.getInitWeekdays());
        } else {
            field.setWeekdays(new ArrayList(Integer.parseInt(result.get(5))));
        }
        return field;
    }

    private static List<Integer> initArray(Integer num) {
        List<Integer> result =new ArrayList<>(num);
        for (int i = 0; i <= num; i++) {
            result.add(i);
        }
        return result;
    }

    private static List<Integer> getInitMinutes() {
        return CronUtil.initArray(59);
    }

    private static List<Integer> getInitHours() {
        return CronUtil.initArray(23);
    }

    private static List<Integer> getInitWeekdays() {
        return CronUtil.initArray(7).subList(1, 8);

    }

    @Data
    public static class CustomCronField {
        private List<Integer> minutes;
        private List<Integer> hours;
        private List<Integer> weekdays;
    }

    /**
     * 根据cron表达式解析最近几次的执行时间
     * @param cronStr
     * @param num
     * @return
     */
    public static List<String> getExecutionTimeByNum(String cronStr, Integer num) {
        CronParser parser = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ));
        Cron cron = parser.parse(cronStr);
        ExecutionTime time = ExecutionTime.forCron(cron);
        ZonedDateTime now = ZonedDateTime.now();
        ZonedDateTime next = getNext(time, now);
        List<ZonedDateTime> timeList = new ArrayList<>(num);
        timeList.add(next);
        for (int i = 1; i < num; i++) {
            next = getNext(time, next);
            timeList.add(next);
        }
        DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        List<String> resultList = new ArrayList<>(num);
        for (ZonedDateTime item : timeList) {
            String result = item.format(format);
            resultList.add(result);
        }
        return resultList;
    }

    private static ZonedDateTime getNext(ExecutionTime time, ZonedDateTime current) {
        return time.nextExecution(current).get();
    }

    /**
     * 判断cron是否是按天执行
     * 如果按天执行cron需以（* * ?）结尾
     * @return true 是以* * ?结尾
     */
    public static Boolean datasetCron(String cron) {
        return StringUtils.isNotBlank(cron) && cron.matches(".* \\* \\* \\?$");
    }
}